// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/quic/quic_crypto_server_stream.h"

#include <memory>

#include "base/base64.h"
#include "crypto/secure_hash.h"
#include "net/quic/crypto/crypto_protocol.h"
#include "net/quic/crypto/crypto_utils.h"
#include "net/quic/crypto/quic_crypto_server_config.h"
#include "net/quic/crypto/quic_random.h"
#include "net/quic/proto/cached_network_parameters.pb.h"
#include "net/quic/quic_config.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_protocol.h"
#include "net/quic/quic_server_session_base.h"

using base::StringPiece;
using std::string;

namespace net {

namespace {
    bool HasFixedTag(const CryptoHandshakeMessage& message)
    {
        const QuicTag* received_tags;
        size_t received_tags_length;
        QuicErrorCode error = message.GetTaglist(kCOPT, &received_tags, &received_tags_length);
        if (error == QUIC_NO_ERROR) {
            DCHECK(received_tags);
            for (size_t i = 0; i < received_tags_length; ++i) {
                if (received_tags[i] == kFIXD) {
                    return true;
                }
            }
        }
        return false;
    }
} // namespace

void ServerHelloNotifier::OnPacketAcked(int acked_bytes,
    QuicTime::Delta ack_delay_time)
{
    DCHECK(!FLAGS_quic_no_shlo_listener);
    // The SHLO is sent in one packet.
    server_stream_->OnServerHelloAcked();
}

void ServerHelloNotifier::OnPacketRetransmitted(int /*retransmitted_bytes*/) { }

QuicCryptoServerStreamBase::QuicCryptoServerStreamBase(
    QuicServerSessionBase* session)
    : QuicCryptoStream(session)
{
}

// TODO(jokulik): Once stateless rejects support is inherent in the version
// number, this function will likely go away entirely.
// static
bool QuicCryptoServerStreamBase::DoesPeerSupportStatelessRejects(
    const CryptoHandshakeMessage& message)
{
    const QuicTag* received_tags;
    size_t received_tags_length;
    QuicErrorCode error = message.GetTaglist(kCOPT, &received_tags, &received_tags_length);
    if (error != QUIC_NO_ERROR) {
        return false;
    }
    for (size_t i = 0; i < received_tags_length; ++i) {
        if (received_tags[i] == kSREJ) {
            return true;
        }
    }
    return false;
}

QuicCryptoServerStream::QuicCryptoServerStream(
    const QuicCryptoServerConfig* crypto_config,
    QuicCompressedCertsCache* compressed_certs_cache,
    bool use_stateless_rejects_if_peer_supported,
    QuicServerSessionBase* session)
    : QuicCryptoServerStreamBase(session)
    , crypto_config_(crypto_config)
    , compressed_certs_cache_(compressed_certs_cache)
    , validate_client_hello_cb_(nullptr)
    , num_handshake_messages_(0)
    , num_handshake_messages_with_server_nonces_(0)
    , num_server_config_update_messages_sent_(0)
    , use_stateless_rejects_if_peer_supported_(
          use_stateless_rejects_if_peer_supported)
    , peer_supports_stateless_rejects_(false)
{
    DCHECK_EQ(Perspective::IS_SERVER, session->connection()->perspective());
}

QuicCryptoServerStream::~QuicCryptoServerStream()
{
    CancelOutstandingCallbacks();
}

void QuicCryptoServerStream::CancelOutstandingCallbacks()
{
    // Detach from the validation callback.  Calling this multiple times is safe.
    if (validate_client_hello_cb_ != nullptr) {
        validate_client_hello_cb_->Cancel();
        validate_client_hello_cb_ = nullptr;
    }
}

void QuicCryptoServerStream::OnHandshakeMessage(
    const CryptoHandshakeMessage& message)
{
    QuicCryptoServerStreamBase::OnHandshakeMessage(message);
    ++num_handshake_messages_;

    // This block should be removed with support for QUIC_VERSION_25.
    if (FLAGS_quic_require_fix && !HasFixedTag(message)) {
        CloseConnectionWithDetails(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND,
            "Missing kFIXD");
        return;
    }

    // Do not process handshake messages after the handshake is confirmed.
    if (handshake_confirmed_) {
        CloseConnectionWithDetails(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE,
            "Unexpected handshake message from client");
        return;
    }

    if (message.tag() != kCHLO) {
        CloseConnectionWithDetails(QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
            "Handshake packet not CHLO");
        return;
    }

    if (validate_client_hello_cb_ != nullptr) {
        // Already processing some other handshake message.  The protocol
        // does not allow for clients to send multiple handshake messages
        // before the server has a chance to respond.
        CloseConnectionWithDetails(
            QUIC_CRYPTO_MESSAGE_WHILE_VALIDATING_CLIENT_HELLO,
            "Unexpected handshake message while processing CHLO");
        return;
    }

    CryptoUtils::HashHandshakeMessage(message, &chlo_hash_);

    validate_client_hello_cb_ = new ValidateCallback(this);
    crypto_config_->ValidateClientHello(
        message, session()->connection()->peer_address().address(),
        session()->connection()->self_address().address(), version(),
        session()->connection()->clock(), &crypto_proof_,
        validate_client_hello_cb_);
}

void QuicCryptoServerStream::FinishProcessingHandshakeMessage(
    const CryptoHandshakeMessage& message,
    const ValidateClientHelloResultCallback::Result& result)
{
    // Clear the callback that got us here.
    DCHECK(validate_client_hello_cb_ != nullptr);
    validate_client_hello_cb_ = nullptr;

    if (use_stateless_rejects_if_peer_supported_) {
        peer_supports_stateless_rejects_ = DoesPeerSupportStatelessRejects(message);
    }

    CryptoHandshakeMessage reply;
    DiversificationNonce diversification_nonce;
    string error_details;
    QuicErrorCode error = ProcessClientHello(
        message, result, &reply, &diversification_nonce, &error_details);

    if (error != QUIC_NO_ERROR) {
        CloseConnectionWithDetails(error, error_details);
        return;
    }

    if (reply.tag() != kSHLO) {
        if (reply.tag() == kSREJ) {
            DCHECK(use_stateless_rejects_if_peer_supported_);
            DCHECK(peer_supports_stateless_rejects_);
            // Before sending the SREJ, cause the connection to save crypto packets
            // so that they can be added to the time wait list manager and
            // retransmitted.
            session()->connection()->EnableSavingCryptoPackets();
        }
        SendHandshakeMessage(reply);

        if (reply.tag() == kSREJ) {
            DCHECK(use_stateless_rejects_if_peer_supported_);
            DCHECK(peer_supports_stateless_rejects_);
            DCHECK(!handshake_confirmed());
            DVLOG(1) << "Closing connection "
                     << session()->connection()->connection_id()
                     << " because of a stateless reject.";
            session()->connection()->CloseConnection(
                QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT, "stateless reject",
                ConnectionCloseBehavior::SILENT_CLOSE);
        }
        return;
    }

    // If we are returning a SHLO then we accepted the handshake.  Now
    // process the negotiated configuration options as part of the
    // session config.
    QuicConfig* config = session()->config();
    OverrideQuicConfigDefaults(config);
    error = config->ProcessPeerHello(message, CLIENT, &error_details);
    if (error != QUIC_NO_ERROR) {
        CloseConnectionWithDetails(error, error_details);
        return;
    }

    session()->OnConfigNegotiated();

    config->ToHandshakeMessage(&reply);

    // Receiving a full CHLO implies the client is prepared to decrypt with
    // the new server write key.  We can start to encrypt with the new server
    // write key.
    //
    // NOTE: the SHLO will be encrypted with the new server write key.
    session()->connection()->SetEncrypter(
        ENCRYPTION_INITIAL,
        crypto_negotiated_params_.initial_crypters.encrypter.release());
    session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
    // Set the decrypter immediately so that we no longer accept unencrypted
    // packets.
    session()->connection()->SetDecrypter(
        ENCRYPTION_INITIAL,
        crypto_negotiated_params_.initial_crypters.decrypter.release());
    if (version() > QUIC_VERSION_32) {
        session()->connection()->SetDiversificationNonce(diversification_nonce);
    }

    // We want to be notified when the SHLO is ACKed so that we can disable
    // HANDSHAKE_MODE in the sent packet manager.
    scoped_refptr<ServerHelloNotifier> server_hello_notifier(
        new ServerHelloNotifier(this));
    SendHandshakeMessage(reply, FLAGS_quic_no_shlo_listener ? nullptr : server_hello_notifier.get());

    session()->connection()->SetEncrypter(
        ENCRYPTION_FORWARD_SECURE,
        crypto_negotiated_params_.forward_secure_crypters.encrypter.release());
    if (FLAGS_quic_default_immediate_forward_secure != config->HasClientSentConnectionOption(kIPFS, Perspective::IS_SERVER)) {
        session()->connection()->SetDefaultEncryptionLevel(
            ENCRYPTION_FORWARD_SECURE);
    }

    session()->connection()->SetAlternativeDecrypter(
        ENCRYPTION_FORWARD_SECURE,
        crypto_negotiated_params_.forward_secure_crypters.decrypter.release(),
        false /* don't latch */);

    encryption_established_ = true;
    handshake_confirmed_ = true;
    session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
}

void QuicCryptoServerStream::SendServerConfigUpdate(
    const CachedNetworkParameters* cached_network_params)
{
    if (!handshake_confirmed_) {
        return;
    }

    CryptoHandshakeMessage server_config_update_message;
    if (!crypto_config_->BuildServerConfigUpdateMessage(
            session()->connection()->version(), chlo_hash_,
            previous_source_address_tokens_,
            session()->connection()->self_address().address(),
            session()->connection()->peer_address().address(),
            session()->connection()->clock(),
            session()->connection()->random_generator(), compressed_certs_cache_,
            crypto_negotiated_params_, cached_network_params,
            &server_config_update_message)) {
        DVLOG(1) << "Server: Failed to build server config update (SCUP)!";
        return;
    }

    DVLOG(1) << "Server: Sending server config update: "
             << server_config_update_message.DebugString();
    const QuicData& data = server_config_update_message.GetSerialized();
    WriteOrBufferData(StringPiece(data.data(), data.length()), false, nullptr);

    ++num_server_config_update_messages_sent_;
}

void QuicCryptoServerStream::OnServerHelloAcked()
{
    session()->connection()->OnHandshakeComplete();
}

uint8_t QuicCryptoServerStream::NumHandshakeMessages() const
{
    return num_handshake_messages_;
}

uint8_t QuicCryptoServerStream::NumHandshakeMessagesWithServerNonces() const
{
    return num_handshake_messages_with_server_nonces_;
}

int QuicCryptoServerStream::NumServerConfigUpdateMessagesSent() const
{
    return num_server_config_update_messages_sent_;
}

const CachedNetworkParameters*
QuicCryptoServerStream::PreviousCachedNetworkParams() const
{
    return previous_cached_network_params_.get();
}

bool QuicCryptoServerStream::UseStatelessRejectsIfPeerSupported() const
{
    return use_stateless_rejects_if_peer_supported_;
}

bool QuicCryptoServerStream::PeerSupportsStatelessRejects() const
{
    return peer_supports_stateless_rejects_;
}

void QuicCryptoServerStream::SetPeerSupportsStatelessRejects(
    bool peer_supports_stateless_rejects)
{
    peer_supports_stateless_rejects_ = peer_supports_stateless_rejects;
}

void QuicCryptoServerStream::SetPreviousCachedNetworkParams(
    CachedNetworkParameters cached_network_params)
{
    previous_cached_network_params_.reset(
        new CachedNetworkParameters(cached_network_params));
}

bool QuicCryptoServerStream::GetBase64SHA256ClientChannelID(
    string* output) const
{
    if (!encryption_established_ || crypto_negotiated_params_.channel_id.empty()) {
        return false;
    }

    const string& channel_id(crypto_negotiated_params_.channel_id);
    std::unique_ptr<crypto::SecureHash> hash(
        crypto::SecureHash::Create(crypto::SecureHash::SHA256));
    hash->Update(channel_id.data(), channel_id.size());
    uint8_t digest[32];
    hash->Finish(digest, sizeof(digest));

    base::Base64Encode(
        string(reinterpret_cast<const char*>(digest), sizeof(digest)), output);
    // Remove padding.
    size_t len = output->size();
    if (len >= 2) {
        if ((*output)[len - 1] == '=') {
            len--;
            if ((*output)[len - 1] == '=') {
                len--;
            }
            output->resize(len);
        }
    }
    return true;
}

QuicErrorCode QuicCryptoServerStream::ProcessClientHello(
    const CryptoHandshakeMessage& message,
    const ValidateClientHelloResultCallback::Result& result,
    CryptoHandshakeMessage* reply,
    DiversificationNonce* out_diversification_nonce,
    string* error_details)
{
    QuicServerSessionBase* session_base = static_cast<QuicServerSessionBase*>(session());
    if (FLAGS_quic_enable_chlo_policy && !session_base->CanAcceptClientHello(message, error_details)) {
        return QUIC_HANDSHAKE_FAILED;
    }

    if (!result.info.server_nonce.empty()) {
        ++num_handshake_messages_with_server_nonces_;
    }
    // Store the bandwidth estimate from the client.
    if (result.cached_network_params.bandwidth_estimate_bytes_per_second() > 0) {
        previous_cached_network_params_.reset(
            new CachedNetworkParameters(result.cached_network_params));
    }
    previous_source_address_tokens_ = result.info.source_address_tokens;

    const bool use_stateless_rejects_in_crypto_config = use_stateless_rejects_if_peer_supported_ && peer_supports_stateless_rejects_;
    QuicConnection* connection = session()->connection();
    const QuicConnectionId server_designated_connection_id = use_stateless_rejects_in_crypto_config
        ? GenerateConnectionIdForReject(connection->connection_id())
        : 0;
    return crypto_config_->ProcessClientHello(
        result, /*reject_only=*/false, connection->connection_id(),
        connection->self_address().address(), connection->peer_address(),
        version(), connection->supported_versions(),
        use_stateless_rejects_in_crypto_config, server_designated_connection_id,
        connection->clock(), connection->random_generator(),
        compressed_certs_cache_, &crypto_negotiated_params_, &crypto_proof_,
        reply, out_diversification_nonce, error_details);
}

void QuicCryptoServerStream::OverrideQuicConfigDefaults(QuicConfig* config) { }

QuicCryptoServerStream::ValidateCallback::ValidateCallback(
    QuicCryptoServerStream* parent)
    : parent_(parent)
{
}

void QuicCryptoServerStream::ValidateCallback::Cancel()
{
    parent_ = nullptr;
}

void QuicCryptoServerStream::ValidateCallback::RunImpl(
    const CryptoHandshakeMessage& client_hello,
    const Result& result)
{
    if (parent_ != nullptr) {
        parent_->FinishProcessingHandshakeMessage(client_hello, result);
    }
}

QuicConnectionId QuicCryptoServerStream::GenerateConnectionIdForReject(
    QuicConnectionId connection_id)
{
    // TODO(rch): Remove this method when this flag is removed.
    if (FLAGS_quic_dispatcher_creates_id) {
        QuicServerSessionBase* session_base = static_cast<QuicServerSessionBase*>(session());
        return session_base->GenerateConnectionIdForReject(connection_id);
    }
    return session()->connection()->random_generator()->RandUint64();
}

} // namespace net
